/*
 * Copyright (C) 2009 Georgios J. Georgopoulos, All rights reserved.
 * 
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option) any
 * later version.
 * 
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
 * site: http://www.fsf.org.
 */
package org.gwt.mosaic.xul.client.application;

import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.gwt.beansbinding.observablecollections.client.ObservableMap;
import org.gwt.beansbinding.observablecollections.client.ObservableMapListener;

/**
 * 
 * @author georgopoulos.georgios(at)gmail.com
 * 
 */
public class ObservableMapBean<K extends Serializable, V extends Serializable>
    extends AbstractMap<K, V> implements ObservableMap<K, V>, Serializable {
  private static final long serialVersionUID = 7505170664616656676L;

  private class EntryIterator implements Iterator<Map.Entry<K, V>> {
    private Iterator<Map.Entry<K, V>> realIterator;
    private Map.Entry<K, V> last;

    EntryIterator() {
      realIterator = properties.entrySet().iterator();
    }

    public boolean hasNext() {
      return realIterator.hasNext();
    }

    public Map.Entry<K, V> next() {
      last = realIterator.next();
      return last;
    }

    public void remove() {
      if (last == null) {
        throw new IllegalStateException();
      }
      Object toRemove = last.getKey();
      last = null;
      ObservableMapBean.this.remove(toRemove);
    }
  }

  private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
    public void clear() {
      ObservableMapBean.this.clear();
    }

    @SuppressWarnings("unchecked")
    public boolean contains(Object o) {
      if (!(o instanceof Map.Entry)) {
        return false;
      }
      Map.Entry<K, V> e = (Map.Entry<K, V>) o;
      return containsKey(e.getKey());
    }

    public Iterator<Map.Entry<K, V>> iterator() {
      return new EntryIterator();
    }

    @SuppressWarnings("unchecked")
    public boolean remove(Object o) {
      if (o instanceof Map.Entry) {
        K key = ((Map.Entry<K, V>) o).getKey();
        if (containsKey(key)) {
          remove(key);
          return true;
        }
      }
      return false;
    }

    public int size() {
      return ObservableMapBean.this.size();
    }
  }

  private Map<K, V> properties = new HashMap<K, V>();
  private Set<Map.Entry<K, V>> entrySet;

  private transient List<ObservableMapListener> listeners = new ArrayList<ObservableMapListener>();

  public ObservableMapBean() {
    super();
  }

  public void addObservableMapListener(ObservableMapListener listener) {
    listeners.add(listener);
  }

  public void removeObservableMapListener(ObservableMapListener listener) {
    listeners.remove(listener);
  }

  @Override
  public void clear() {
    // Remove all elements via iterator to trigger notification
    Iterator<K> iterator = keySet().iterator();
    while (iterator.hasNext()) {
      iterator.next();
      iterator.remove();
    }
  }

  @Override
  public boolean containsKey(Object key) {
    return properties.containsKey(key);
  }

  @Override
  public boolean containsValue(Object value) {
    return properties.containsKey(value);
  }

  @Override
  public Set<Map.Entry<K, V>> entrySet() {
    Set<Map.Entry<K, V>> es = entrySet;
    return es != null ? es : (entrySet = new EntrySet());
  }

  @Override
  public V get(Object key) {
    return properties.get(key);
  }

  @Override
  public boolean isEmpty() {
    return properties.isEmpty();
  }

  @Override
  public V put(K key, V value) {
    V lastValue;
    if (containsKey(key)) {
      lastValue = properties.put(key, value);
      for (ObservableMapListener listener : listeners) {
        listener.mapKeyValueChanged(this, key, lastValue);
      }
    } else {
      lastValue = properties.put(key, value);
      for (ObservableMapListener listener : listeners) {
        listener.mapKeyAdded(this, key);
      }
    }
    return lastValue;
  }

  @Override
  public void putAll(Map<? extends K, ? extends V> m) {
    for (K key : m.keySet()) {
      put(key, m.get(key));
    }
  }

  @Override
  public V remove(Object key) {
    if (containsKey(key)) {
      V value = properties.remove(key);
      for (ObservableMapListener listener : listeners) {
        listener.mapKeyRemoved(this, key, value);
      }
      return value;
    }
    return null;
  }

  @Override
  public int size() {
    return properties.size();
  }

  @Override
  public Collection<V> values() {
    return properties.values();
  }
}
